package ex3.render.raytrace;

import java.util.Map;
import java.util.Scanner;


import math.*;

public class Triangle extends Surface {
	// constant to be used to represent no intersection
	
	private static final double NO_INTERSECTION = -2;
	
	// three points of the triangle surface object
	private Point3D[] trianglePoints = new Point3D[3];

	// the triangle number in a trimesh, made public to be computed quickly
	public int triNumber;

	public Triangle(Map<String, String> attributes, int triangleNumber) {
		super(attributes);
		this.triNumber = triangleNumber;
		// get the attributes of the specified triangle number
		String triInfo = attributes.get("tri" + triNumber);
		initTriangle(triInfo);
	}

	/**
	 * set the triangle fields
	 * 
	 * @param getTriangle
	 */
	public void initTriangle(String triInfo) {
		// parse triangle XML data
		Scanner scan = new Scanner(triInfo);
		// get triangle coordinates

		for (int i = 0; i < 3; i++) {
			// create generic point then scan attributes and update
			Point3D point = new Point3D();
			point.x = scan.nextDouble();
			point.y = scan.nextDouble();
			point.z = scan.nextDouble();
			// move onto next point in triangle
			this.trianglePoints[i] = point;
		}
	}

	/**
	 * Constructor using 3 provided 3D points
	 * 
	 * @param p0
	 * @param p1
	 * @param p2
	 */
	public Triangle(Point3D p0, Point3D p1, Point3D p2) {
		this.trianglePoints[0] = p0;
		this.trianglePoints[1] = p1;
		this.trianglePoints[2] = p2;
	}

	/**
	 * Ray triangle intersection, returns distance
	 */
	public double nearestIntersection(Ray ray, boolean backFace) {
		
		// Ray-Triangle intersection method shown in class (Algebraic)
		Vec v1 = Point3D.subtractPoints(this.trianglePoints[0], this.trianglePoints[1]);
		Vec v2 = Point3D.subtractPoints(this.trianglePoints[0], this.trianglePoints[2]);
		
		/*  v2 x v1
		 * -------
		 * |v2 x v1| 
		 */
		Vec crossOneTwo = Vec.crossProd(v1, v2);
		crossOneTwo.normalize();
		
		Vec normalized = crossOneTwo;
		double N = Vec.dotProd(ray.v, normalized);
		
		if (N == 0) {
			return NO_INTERSECTION;
		}
		
		Vec v3 = Point3D.subtractPoints(trianglePoints[0], ray.p);
		double distance = Vec.dotProd(normalized, v3);	
		
		distance /= N;
		// if (P - P0 (dot) N < 0, return false as there is no intersection
		if (distance < 0) {
			return NO_INTERSECTION;
		}

		// checks if the hit is in the boundaries of the triangle
		Point3D intersectionPoint = Point3D.nearestIntersectionPoint(ray, distance);
		
		// we check if (P - P0 (dot) N < 0 for all three sides of a triangle
		Vec v3m2 = Point3D.subtractPoints(trianglePoints[2], trianglePoints[1]);
		Vec v1m3 = Point3D.subtractPoints(trianglePoints[0], trianglePoints[2]);
		Vec v2m1 = Point3D.subtractPoints(trianglePoints[1], trianglePoints[0]);
		
		// check the intersections
		Vec intersect1 = Point3D.subtractPoints(intersectionPoint, trianglePoints[0]);
		Vec intersect2 = Point3D.subtractPoints(intersectionPoint, trianglePoints[1]);
		Vec intersect3 = Point3D.subtractPoints(intersectionPoint, trianglePoints[2]);
		
		
		Vec c12 = Vec.crossProd(v2m1, intersect1);
		double result12 = Vec.dotProd(c12, normalAt(null, ray));

		Vec c23 = Vec.crossProd(v3m2, intersect2);
		double result23 = Vec.dotProd(c23, normalAt(null, ray));

		Vec c13 = Vec.crossProd(v1m3, intersect3);
		double result13 = Vec.dotProd(c13, normalAt(null, ray));
		
		// Check if we are accidently rendering the back face of the triangle
		if (backFace == (Vec.dotProd(ray.v, crossOneTwo) < 0)) {
			return NO_INTERSECTION;
		}

		// if all three sides of the triangles have results >= 0, then we have an intersection!
		if ((result12 >= 0) && (result23 >= 0) && (result13 >= 0)){
			return distance;
		} else {
			return NO_INTERSECTION;
		}
	}

	/**
	 * Helper method used to find the point at which a ray intersects a given plane vector
	 * @param point
	 * @param planeNorm
	 * @param ray
	 * @return, the 3D point representing the itnersection between the given ray and plane
	 */
	public Point3D findRayPlaneIntersection(Point3D point,
			Vec planeNorm, Ray ray) {
		// ray-plane intersection as learned in class
		
		double vecDotNplane = Vec.dotProd(ray.v, planeNorm);
		
		// if dot product = 0, no intersection point to return, return null
		if (vecDotNplane == 0) {
			return null;
		}
		
		// vector connecting the plane to the ray
		Vec planeRay = Point3D.sub(point, ray.p);
		// dot product of the plane ray with normal vec
		double planeRayNorm = Vec.dotProd(planeRay, planeNorm);
		
		// no intersection
		if (planeRayNorm / vecDotNplane < 0) {
			return null;
		} else { // exist an intersecting point
			Point3D intersection = new Point3D(ray.p);
			intersection.mac(planeRayNorm / vecDotNplane,
					ray.v);
			return intersection;
		}
	}

	/**
	 * Standard normal vector to intersection
	 */
	public Vec normalAt(Point3D intersection, Ray ray) {
		Vec vec1 = Point3D.sub(this.trianglePoints[1], this.trianglePoints[0]);
		Vec vec2 = Point3D.sub(this.trianglePoints[2], this.trianglePoints[0]);
		Vec normal = Vec.crossProd(vec1, vec2);
		normal.normalize();
		return normal;
	}
	
	/**
	 * Generic constructor to be used in conjunction with init
	 */
	public Triangle() {
	}

	public void init(Map<String, String> attributes, int triangleNumber) {
		this.triNumber = triangleNumber;
		// get the attributes of the specified triangle number
		String triInfo = attributes.get("tri" + triNumber);
		initTriangle(triInfo);
	}
}